/*
	flad : Copyright 2010 Dale J Williams

	Licensed under the Apache License, Version 2.0 (the "License");
	you may not use this file except in compliance with the License.
	You may obtain a copy of the License at

	http://www.apache.org/licenses/LICENSE-2.0

	Unless required by applicable law or agreed to in writing, software
	distributed under the License is distributed on an "AS IS" BASIS,
	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	See the License for the specific language governing permissions and
	limitations under the License.
*/
package lads.flad.display {
	
	import lads.flad.interfaces.IWidget;
	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.events.Event;
	
	/**
	 * The Widget class is a blank container designed to override inbuilt resizing 
	 * methods. A Widget can be extended in place of a Sprite where redrawing is to
	 * be automatically handled.
	 * 
	 * Widgets are practical for the creation of scalable user interface elements.
	 * 
	 * @author Dale J Williams
	 * @version 2.0
	 */
	public class Widget extends Sprite implements IWidget {

		protected var _width				:Number		= 0;
		protected var _height				:Number		= 0;
		private var _onDraw					:Function	= null;
		private var _stageResizable			:Boolean;
		private var _onAddedToStage			:Function;
		private var _onRemovedFromStage		:Function;
		private var _managedListeners		:Array;
		
		/**
		 * Widgets are designed to be blank on instanciation. Though a drawing
		 * function can be set at this stage, it is not required or recommended.
		 * 
		 * @param	on_draw		Will set the drawing function on instanciation. This
		 * is not required and can set later using the <code>onDraw</code> setting.
		 */
		public function Widget(on_draw:Function = null) {
			if(on_draw != null)
				onDraw = on_draw;
		}
		
		/**
		* drawNextRender can be called whenever a change requires the Widget to need 
		* to redraw. This is the recommended drawing function.
		* 
		* The drawNextRender function will call the onDraw function, if set, on next
		* render.
		*/
		public function drawNextRender():void {
			if (!stage) return
			super.addEventListener(Event.RENDER, _callDraw, false, 0, true);
			super.addEventListener(Event.ENTER_FRAME, _callDraw, false, 0, true);
			stage.invalidate();
		}
		
		public function cancelNextRender():void {
			if (!stage) return;
			if (hasEventListener(Event.RENDER)) super.removeEventListener(Event.RENDER, _callDraw);
			if (hasEventListener(Event.ENTER_FRAME)) super.removeEventListener(Event.ENTER_FRAME, _callDraw);
		}
		
		/**
		* Forces the onDraw function to be called immediately. Calling redraw instead 
		* of drawNextRender will force the visual update before the next render. This
		* method will override stage management.
		*/
		public function redraw():void {
			cancelNextRender();
			if (_onDraw != null)
				_onDraw();
		}
		
		public function manageEventListener(type:String, listener:Function, target:Object = null):void {
			if (!target) target = this;
			if (!_managedListeners) _managedListeners = [];
			_managedListeners.push([type, listener, target]);
			if (stage) target.addEventListener(type, listener);
			_manageStageFunctions();
		}
		
		public function unmanageEventListener(type:String, listener:Function, target:Object = null):void {
			if (!target) target = this;
			if (target.hasEventListener(type)) {		
				if (_managedListeners) {
					var managedListener:Object;
					for (var i:int = 0; i < _managedListeners.length; i++) {
						managedListener = _managedListeners[i];
						if (managedListener[0] == type && managedListener[1] == listener)
						_managedListeners.splice(i, 1);
						target.removeEventListener(type, listener);
					}
				}
			}
		}
		
		public function sizeTo(target:DisplayObject):void {
			_width = target.width;
			_height = target.height;
			drawNextRender();
		}
		
		public function sizeToStage($drawNextRender:Boolean=false):void {
			_width = stage.stageWidth;
			_height = stage.stageHeight;
			if ($drawNextRender) drawNextRender();
		}
		
		private function _callDraw(e:Event = null):void {
			if (e) cancelNextRender();
			if (!stage) return;
			if (_stageResizable) {
				_width = stage.stageWidth;
				_height = stage.stageHeight;
			}
			if (_onDraw != null) _onDraw();
		}
		
		private function _manageStageResize(e:Event=null):void {
			if (stage && _stageResizable)
				stage.addEventListener(Event.RESIZE, _callDraw);
			else if (e) stage.removeEventListener(Event.RESIZE, _callDraw);
		}
		
		/**
		* If set to true, the Widget will automatically call the drawing function.
		*/
		public function set stageResizable(value:Boolean):void {
			_stageResizable = value;
			if(value){
				super.addEventListener(Event.ADDED_TO_STAGE, _manageStageResize);
				super.addEventListener(Event.REMOVED_FROM_STAGE, _manageStageResize);
			} else {
				super.removeEventListener(Event.ADDED_TO_STAGE, _manageStageResize);
				super.removeEventListener(Event.REMOVED_FROM_STAGE, _manageStageResize);
			}
			_manageStageResize();
		}
		
		/**
		 * onDraw is the drawing function. The drawing function is called whenever 
		 * Widget renders, provided it is set. It is good practice to set this value
		 * in your Widget's constructor.
		 */
		public function set onDraw(value:Function):void {
			if (value != null) 
				super.addEventListener(Event.ADDED_TO_STAGE, _callDraw);
			else super.removeEventListener(Event.ADDED_TO_STAGE, _callDraw);
			_onDraw = value;
			_callDraw();
		}		
		
		private function _callAddedToStage(e:Event = null):void {
			for each(var managedListener:Array in _managedListeners)
				managedListener[2].addEventListener(managedListener[0], managedListener[1]);
			if (_onAddedToStage != null)
				_onAddedToStage();
		}
		
		private function _callRemovedFromStage(e:Event = null):void {
			for each(var managedListener:Array in _managedListeners)
				managedListener[2].removeEventListener(managedListener[0], managedListener[1]);
			if (_onRemovedFromStage != null)
				_onRemovedFromStage();
		}
		
		private function _manageStageFunctions(e:Event=null):void {
			addEventListener(Event.ADDED_TO_STAGE, _callAddedToStage);
			addEventListener(Event.REMOVED_FROM_STAGE, _callRemovedFromStage);
		}
		
		/**
		 * The onAddedToStage method is called when the StageWidget is added to the
		 * stage, provided it is set. It is good practice to set this value
		 * in your StageWidget's constructor.
		 */
		public function set onAddedToStage(value:Function):void {
			_onAddedToStage = value;
			_manageStageFunctions();
		}
		
		/**
		 * The onRemovedFromStage method is called when the StageWidget is added to 
		 * the stage, provided it is set. It is good practice to set this value
		 * in your StageWidget's constructor.
		 */
		public function set onRemovedFromStage(value:Function):void {
			_onRemovedFromStage = value;
			_manageStageFunctions();
		}
		
		/**
		 * A Widget's width value must be set by either itself or its container. 
		 * 
		 * Without a width and height a Widget is invisible. The behaviour of width
		 * overrides Sprite's default width behaviour.
		 * 
		 * Setting width will cause callDrawNextRender to be called.
		 */
		override public function get width():Number { return _width; }
		override public function set width(value:Number):void {
			_width = value;
			drawNextRender();
		}	
		
		/**
		 * A Widget's height value must be set by either itself or its container. 
		 * 
		 * Without a height and width a Widget is invisible. The behaviour of height
		 * overrides Sprite's default height behaviour.
		 * 
		 * Setting height will cause drawNextRender to be called.
		 */
		override public function get height():Number { return _height; }
		override public function set height(value:Number):void {
			_height = value;
			drawNextRender();
		}	
		
		/**
		 * The superWidth value can be used to access the Sprite's native width parameter. 
		 * 
		 * This will access the player's native scaling behaviour and should not be used at 
		 * the same time as the overriden width value.
		 */
		public function get superWidth():Number { return super.width; }
		public function set superWidth(value:Number):void {
			super.width = value;
		}	
		
		/**
		 * The superHeight value can be used to access the Sprite's native height parameter. 
		 * 
		 * This will access the player's native scaling behaviour and should not be used at 
		 * the same time as the overriden height value.
		 */
		public function get superHeight():Number { return super.height; }
		public function set superHeight(value:Number):void {
			super.height = value;
		}
		
	}
	
}